﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;

namespace Ignitron.EEngine.Adapters.OpenGL
{
    /// <summary>
    /// Class representing the render-to texture
    /// </summary>
    internal class OpenTKRenderTexture: OpenTKTexture
    {
        /// <summary>
        /// The frame buffer id
        /// </summary>
        private int mFrameBufferId;
        private int mDepthBufferId;

        /// <summary>
        /// The size of the render texture
        /// </summary>
        private int mWidth;
        private int mHeight;

        /// <summary>
        /// Constructor - initializes resources for the render texture
        /// </summary>
        internal OpenTKRenderTexture(int width, int height)
        {
            mWidth = width;
            mHeight = height;

            // generate dynamic texture
            GL.GenTextures(1, out mTextureIndex);
            GL.BindTexture(TextureTarget.Texture2D, mTextureIndex);
            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (float)TextureEnvMode.Replace);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (float)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (float)TextureMagFilter.Linear);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, width, height, 0,
                 OpenTK.Graphics.OpenGL.PixelFormat.Bgr, PixelType.UnsignedByte, IntPtr.Zero);
            GL.BindTexture(TextureTarget.Texture2D, 0);

            // generate FBO depth buffer
            GL.GenRenderbuffers(1, out mDepthBufferId);
            GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, mDepthBufferId);
            GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.DepthComponent16, width, height);
            GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);

            // create frame buffer
            GL.GenFramebuffers(1, out mFrameBufferId);
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, mFrameBufferId);
            GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0,
                    TextureTarget.Texture2D, mTextureIndex, 0);
            GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment,
                                     RenderbufferTarget.Renderbuffer, mDepthBufferId);
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
        }

        /// <summary>
        /// Frees the resources
        /// </summary>
        internal void Shutdown()
        {
            GL.DeleteFramebuffers(1, ref mFrameBufferId);
            GL.DeleteTexture(mTextureIndex);
            GL.DeleteRenderbuffers(1, ref mDepthBufferId);
        }

        /// <summary>
        /// Prepares the viewport for 2D rendering
        /// </summary>
        internal void PrepareViewport()
        {
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, mFrameBufferId);

            GL.MatrixMode(MatrixMode.Projection);
            GL.PushMatrix();
            GL.LoadIdentity();
            GL.Ortho(0, mWidth, 0, mHeight, -1, 1);
            GL.Viewport(0, 0, mHeight, mHeight);

            GL.MatrixMode(MatrixMode.Modelview);
            GL.PushMatrix();
            GL.LoadIdentity();
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            GL.Disable(EnableCap.Lighting);
        }

        /// <summary>
        /// Restores the previous viewport
        /// TO-DO: Re-render background in the caller
        /// </summary>
        internal void RestoreViewport(int width, int height, bool enabledLighting)
        {
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);

            GL.MatrixMode(MatrixMode.Projection);
            GL.PopMatrix();
            //GL.Viewport(0, 0, w * samples, h * samples);

            // do centering
            int size = Math.Min(width, height);
            double left = -0.5 * ((double)width / (double)size);
            double right = 0.5 * ((double)height / (double)size);

            //GL.Frustum(left, right, -.5, .5, 5, 20);
            GL.Viewport(0, 0, width, height); // Use all of the glControl painting area

            GL.ClearColor(Color.FromArgb(239, 239, 239));
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            GL.MatrixMode(MatrixMode.Modelview);
            GL.PopMatrix();

            if (enabledLighting)
                GL.Enable(EnableCap.Lighting);
        }

        /// <summary>
        /// Renders a filled rect
        /// </summary>
        internal void DrawRect(int x, int y, int width, int height, Vector4 color)
        {
            GL.MatrixMode(MatrixMode.Modelview);
            GL.PushMatrix();
            GL.LoadIdentity();
            GL.Disable(EnableCap.Texture2D);
            GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
            GL.PushAttrib(AttribMask.CurrentBit);
            GL.Color4(color);
            GL.Begin(PrimitiveType.Quads);
            GL.Vertex3(x, y, 0.5f);
            GL.Vertex3(x, y + height + 1, 0.5f);
            GL.Vertex3(x + width, y + height + 1, 0.5f);
            GL.Vertex3(x + width + 1, y, 0.5f);
            GL.End();
            GL.PopAttrib();
            GL.PopMatrix();
            GL.Enable(EnableCap.Texture2D);
        }

        /// <summary>
        /// Renders a texture
        /// </summary>
        internal void DrawTexture(int x, int y, int width, int height, OpenTKTexture texture)
        {
            var _ptrTexture = texture.Bitmap.LockBits(new Rectangle(0, 0, texture.Bitmap.Width, texture.Bitmap.Height),
                ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            
            int tmpIdx;
            GL.GenTextures(1, out tmpIdx);
            GL.BindTexture(TextureTarget.Texture2D, tmpIdx);
            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (float)TextureEnvMode.Replace);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (float)TextureMinFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (float)TextureMagFilter.Nearest);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Three, _ptrTexture.Width, _ptrTexture.Height,
                0, OpenTK.Graphics.OpenGL.PixelFormat.Bgr, PixelType.UnsignedByte, _ptrTexture.Scan0);
            GL.Begin(PrimitiveType.Quads);

            GL.TexCoord2(0f, 0f);
            GL.Vertex3(x, y, 0.3f);
            GL.TexCoord2(0f, 1f);
            GL.Vertex3(x, y + height, 0.3f);
            GL.TexCoord2(1f, 1f);
            GL.Vertex3(x + width, y + height, 0.3f);
            GL.TexCoord2(1f, 0f);
            GL.Vertex3(x + width, y, 0.3f);

            GL.End();

            texture.Bitmap.UnlockBits(_ptrTexture);
            GL.DeleteTexture(tmpIdx);
        }

        /// <summary>
        /// Binds the texture for rendering
        /// </summary>
        internal override void BindTexture(bool lightingEnabled)
        {
            GL.BindTexture(TextureTarget.Texture2D, mTextureIndex);
            
            if (lightingEnabled)
                GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (float)TextureEnvMode.Modulate);
            else
                GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (float)TextureEnvMode.Replace);

            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (float)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (float)TextureMagFilter.Linear);
        }

        /// <summary>
        /// Frees the resources after binding
        /// </summary>
        internal override void FreeAfterBinding()
        {
            GL.BindTexture(TextureTarget.Texture2D, 0);
        }
    }
}
